"""
Copyright 2012 Wieser Dietmar, Margit Mutschlechner.

This file is part of Multimedia Social Networking Platform.

Multimedia Social Networking Platform is free software: 
you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Multimedia Social Networking Platform is distributed
in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Multimedia Social Networking Platform.  
If not, see <http://www.gnu.org/licenses/>.

"""

from threading import Timer
from msnp.service import network
from msnp.logger import Logger
from msnp.service.network import logic
from msnp.service.network import sending
from msnp.service.network import NetworkLock, networktimer
from msnp.service.network.logic import ResponsibilityLogic
import traceback

#queue for all running crash timers
__crashQueue = {}

    
def startCallbacksReceivedTimer(crashedIdentity):
    """This function starts the crash timer for the given crashed identity.
    
    A new CrashQueueEntry is created and added to the queue.
    If there exists already an entry for the given identity the call is ignored.
    
    Args:
        crashedIdentity: the crashed peer (PeerInfo instance)
    """
    
    global __crashQueue
    NetworkLock.acquireCrashFinishLock()
    try:
        crashedIdentityName = crashedIdentity.getName()
        crashQueueEntry = __crashQueue.get(crashedIdentityName)
        if crashQueueEntry == None:
            Logger.info("Start crashtimer for " + crashedIdentityName)
            __crashQueue[crashedIdentityName] = CrashQueueEntry(crashedIdentity)
        else:
            Logger.info("Crashtimer already started for " + crashedIdentityName)
    finally:
        NetworkLock.releaseCrashFinishLock()


def newCrashBroadcastCallback(crashedIdentity, newIdentity, successorOfCrashPeerFound):
    """This function is called after a reception of new identity.
    
    If the crash queue has already an entry for the given crashed identity it checks whether the new identity
    is a better choice than the current 'bestCrashBroadcastCallback'.
    If 'successorOfCrashPeerFound' is True the allCrashCallbacksReceived function of the crash queue entry is called, 
    otherwise the timer is started again.
    
    Args:
        crashedIdentity: the crashed peer (PeerInfo instance)
        newIdentity: the new peer (PeerInfo instance)
        successorOfCrashPeerFound: boolean determining whether the successor of the crashed peer is already found.
    """
    
    global __crashQueue
    
    NetworkLock.acquireCrashTimerLock()
    try:
        crashQueueEntry = __crashQueue.get(crashedIdentity.getName())
        if crashQueueEntry != None:
            Logger.info("Got crash callback from " + newIdentity.getName() + " for crashedIdentity " + crashedIdentity.getName())
            if crashedIdentity.getHashedNameLong() != newIdentity.getHashedNameLong():
                bestCrashBroadcastCallback = crashQueueEntry.getBestCrashBroadcastCallback()
                #check whether newIdentity is better than the old bestCrashBroadcastCallback
                if bestCrashBroadcastCallback == None or ResponsibilityLogic.isResponsible(crashedIdentity.getHashedNameLong(), newIdentity.getHashedNameLong(), bestCrashBroadcastCallback.getHashedNameLong()):
                    Logger.info("set " + newIdentity.getName() + " as best crashbroadcastcallback")
                    crashQueueEntry.setBestCrashBroadcastCallback(newIdentity)
                    if successorOfCrashPeerFound:
                        Logger.info(" with successor found")
                        if sending.Sender.isAlive(crashedIdentity) == False:
                            crashQueueEntry.allCrashCallbacksReceived()
                #restart timer
                crashQueueEntry.startAllCallbacksReceivedTimer()
            else:
                #crashedIdentity sent backup -> is still alive, remove entry from queue
                removeCrashQueueEntry(crashedIdentity.getName())
        else:
            Logger.info("No crashtimer running for " + crashedIdentity.getName())
    finally:
        NetworkLock.releaseCrashTimerLock()
    
    
def removeCrashQueueEntry(crashedIdentityName):
    """This function removes a certain entry from the crash queue.
    
    Args:
        crashedIdentityName: the name of the peer (string)
    """
    
    global __crashQueue
    try:
        __crashQueue.pop(crashedIdentityName)
    except:
        traceback.print_exc()
    

class CrashQueueEntry():
    """Class for the entries of the crash queue.

    Attributes:
        __crashedIdentity: the crashed identity (PeerInfo instance)
        __bestCrashBroadcastCallback: peer that suits the best as new successor (PeerInfo instance)
        __timerInterval: if no callback arrives in that time the timer will be stopped (float - 15.0 standard)
        __retryCount: 3 tries standard
        __allCallbacksReceivedTimer: the Timer instance
    """
    
    def __init__(self, crashedIdentity, retryCount=3):
        """the init method"""
        
        self.__crashedIdentity = crashedIdentity
        self.__bestCrashBroadcastCallback = None
        self.__timerInterval = 15.0
        self.__retryCount = retryCount
        self.__allCallbacksReceivedTimer = None
        self.startAllCallbacksReceivedTimer()
        
        
    def getBestCrashBroadcastCallback(self):
        """Returns the best callback identity."""
        return self.__bestCrashBroadcastCallback
    
    def setBestCrashBroadcastCallback(self, bestCrashBroadcastCallback):
        """Sets the best callback identity."""
        self.__bestCrashBroadcastCallback = bestCrashBroadcastCallback
        
    def startAllCallbacksReceivedTimer(self):
        """This method starts the timer. 
        
        If the timer is already running it will be restarted."""
        
        if self.__allCallbacksReceivedTimer != None:
            self.__allCallbacksReceivedTimer.cancel()
        self.__allCallbacksReceivedTimer = Timer(self.__timerInterval, self.allCrashCallbacksReceived)
        self.__allCallbacksReceivedTimer.start()
    
    
    def allCrashCallbacksReceived(self):
        """This method is executed when the crash timer is finished.
        
        If the peer received at least one callback the bestCrashBroadcastCallback will be set as new successor and the reconnect ring function is called.
        Otherwise this value will be None, which means all connections to other peers are lost and the peer is the only peer in the ring.
        The retry count gets decreased by 1. If it is greater than 0 the timer will be restarted, otherwise all fingers are reset."""
        
        NetworkLock.acquireCrashFinishLock()
        try:
            Logger.info("all crash callbacks received for " + self.__crashedIdentity.getName())
            #close ring again if I got a callback
            if self.__bestCrashBroadcastCallback != None:
                #do this before, because there could be another handlecrash while sending updatepredecessorofsuccessor
                networktimer.CrashTimer.removeCrashQueueEntry(self.__crashedIdentity.getName())
                Logger.info("reconnect the ring")
                logic.CrashLogic.reconnectRing(self.__bestCrashBroadcastCallback)
            #I didn't get any callbacks -> I am the only peer in the ring
            else:
                self.__retryCount = self.__retryCount - 1
                if self.__retryCount == 0:
                    networktimer.CrashTimer.removeCrashQueueEntry(self.__crashedIdentity.getName())
                    Logger.info("best crash callback is None after 3 tries -> reset my fingers")
                    network.NetworkController.getMySelf().initFingers()
                    network.NetworkController.getMySelf().setPredecessor(None)
                else:
                    Logger.info("First crashtimer try finished, retrycount = " + str(self.__retryCount) + ". restart timer.")
                    self.startAllCallbacksReceivedTimer()
        finally:
            NetworkLock.releaseCrashFinishLock()
